/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.common.core.io.supplier; import java.io.BufferedInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import com.google.common.io.ByteStreams; import com.google.common.io.CountingInputStream; /** * Provides input supplier based on a single input stream, that allow to consume * it multiple times up to a limit in read bytes (see * {@link #getLookupSupplier()}), and once completely (see * {@link #getInputSupplier()}). For the underlying input stream to be closed, * the input stream provided by {@link #getInputSupplier()} must be closed. * * @author Simon Templer */ public class LookupStreamResource { /** * Stream that prevents mark and reset being called, as this should be * controlled by the {@link LookupStreamResource}. */ private static class PreventMark extends FilterInputStream { /** * @see FilterInputStream#FilterInputStream(InputStream) */ protected PreventMark(InputStream in) { super(in); } /** * @see java.io.FilterInputStream#mark(int) */ @Override public synchronized void mark(int readlimit) { // ignore } /** * @see java.io.FilterInputStream#reset() */ @Override public synchronized void reset() throws IOException { throw new IOException("mark not supported"); } /** * @see java.io.FilterInputStream#markSupported() */ @Override public boolean markSupported() { return false; } } private final CountingInputStream input; private final URI location; private final int lookupLimit; /** * Constructor. * * @param input the input stream * @param location the location represented by the input stream, may be * <code>null</code> * @param lookupLimit the limit of bytes that may be read from a lookup * input stream */ public LookupStreamResource(InputStream input, URI location, int lookupLimit) { super(); this.location = location; this.lookupLimit = lookupLimit; if (!input.markSupported()) { // add mark support input = new BufferedInputStream(input); } this.input = new CountingInputStream(input); this.input.mark(lookupLimit); } /** * Get an input supplier that supplies streams that may only be read to a * certain amount of bytes. Only one instance of such a stream may be used * at a time (as they are all backed by the same stream) and the instance * should be closed before the stream is retrieved through * {@link #getInputSupplier()}. * * @return the input supplier */ public LocatableInputSupplier<? extends InputStream> getLookupSupplier() { return new LocatableInputSupplier<InputStream>() { @Override public InputStream getInput() throws IOException { input.reset(); return new PreventMark( new FilterInputStream(ByteStreams.limit(input, lookupLimit)) { @Override public void close() throws IOException { // don't close stream, reset instead input.reset(); } }); } @Override public URI getLocation() { return location; } @Override public URI getUsedLocation() { return getLocation(); } }; } /** * Get an input supplier that supplies the underlying stream, which can be * fully consumed only once. * * @return the input supplier */ public LocatableInputSupplier<? extends InputStream> getInputSupplier() { return new LocatableInputSupplier<InputStream>() { @Override public InputStream getInput() throws IOException { if (input.getCount() > lookupLimit) { throw new IllegalStateException("Input stream can only be consumed once."); } input.reset(); return new PreventMark(new FilterInputStream(input) { /** * @see java.io.FilterInputStream#close() */ @Override public void close() throws IOException { if (((CountingInputStream) in).getCount() > lookupLimit) { // close only if lookupLimit has been exceeded super.close(); } else { // otherwise reset reset(); } } @Override protected void finalize() throws Throwable { super.finalize(); // close the underlying stream if not yet done super.close(); } }); } @Override public URI getLocation() { return location; } @Override public URI getUsedLocation() { return getLocation(); } }; } }